package com.springboot.frame.starter.swagger;


import org.apache.commons.lang3.reflect.FieldUtils;
import io.swagger.annotations.ApiOperation;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpMethod;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.*;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import java.lang.reflect.Field;
import java.util.*;

/**
 * swagger3 的spring-boot启动器
 *
 * @author liheng
 */
@Configuration
@ConditionalOnProperty(name = "web.swagger.enabled")
@EnableConfigurationProperties(SwaggerProperties.class)
@EnableOpenApi
@ConditionalOnClass(Docket.class)
@Profile({"dev", "test"})
public class SwaggerAutoConfiguration implements WebMvcConfigurer/*, BeanFactoryPostProcessor */ {

    private final SwaggerProperties swaggerProperties;

    public SwaggerAutoConfiguration(SwaggerProperties swaggerProperties) {
        this.swaggerProperties = swaggerProperties;
    }

    /**
     * 文档相关资源路径
     */
    private static final String[] DOC_LIST =
            {
                    "/favicon.ico", "/v3/**", "/v2/**",
                    "/error", "/swagger**/**", "/configuration/ui",
                    "/configuration/security", "/webjars/**", "/doc**/**"
            };

    /**
     * 支持的通讯协议集合
     *
     * @return
     */
    private Set<String> protocols() {
        Set<String> set = new HashSet<>();
        set.add("https");
        set.add("http");
        return set;
    }

    /**
     * 响应状态集合
     *
     * @return
     */
    private List<Response> responseList() {
        List<Response> responseList = new ArrayList<>();
        Arrays.stream(GlobalResultEnum.values()).forEach(errorEnum -> {
            responseList.add(
                    new ResponseBuilder().code(String.valueOf(errorEnum.getCode())).description(errorEnum.getMessage()).build()
            );
        });
        return responseList;
    }

    /**
     * 设置文档基本信息
     *
     * @return
     */
    private ApiInfo apiInfo(SwaggerProperties.Contact contact) {
        SwaggerProperties.Contact cont = Objects.isNull(contact) ? swaggerProperties.getContact() : contact;
        return new ApiInfoBuilder()
                .title(swaggerProperties.getTitle())
                .description(swaggerProperties.getDescription())
                .termsOfServiceUrl(swaggerProperties.getServiceUrl())
                .contact(new Contact(cont.getName(), cont.getUrl(), cont.getEmail()))
                .version(swaggerProperties.getVersion())
                .license(swaggerProperties.getLicense())
                .licenseUrl(swaggerProperties.getLicenseUrl())
                .build();
    }

    /**
     * 生成全局头部通用参数
     *
     * @return
     */
    private List<RequestParameter> setFixedParameter(List<SwaggerProperties.ReqFixedParameter> reqFixedParameters) {
        List<RequestParameter> parameters = new ArrayList<>();
        List<SwaggerProperties.ReqFixedParameter> fixedParameters = CollectionUtils.isEmpty(reqFixedParameters) ? swaggerProperties.getReqFixedParameters() : reqFixedParameters;
        for (SwaggerProperties.ReqFixedParameter reqFixedParameter : fixedParameters) {
            parameters.add(new RequestParameterBuilder()
                    .name(reqFixedParameter.getParamKey())
                    .description(reqFixedParameter.getDescription())
                    .required(reqFixedParameter.isRequired())
                    .in(ParameterType.HEADER)
                    .build());
        }
        return parameters;
    }

    /**
     * 默认分组
     *
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    public Docket docket() {
        return new Docket(DocumentationType.OAS_30)
                .enable(swaggerProperties.isEnabled())
                .groupName("全部")
                .globalResponses(HttpMethod.GET, responseList())
                .globalResponses(HttpMethod.POST, responseList())
                .globalResponses(HttpMethod.PUT, responseList())
                .globalResponses(HttpMethod.DELETE, responseList())
                .apiInfo(apiInfo(swaggerProperties.getContact()))
                .select()
                // 表示任何包
                // 加了ApiOperation注解的类，才生成接口文档
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                // 避免使用swagger api的默认basic-error-controller
                // .paths(PathSelectors.regex("(?!/error.*).*"))
                .build()
                // .securitySchemes(securitySchemes())
                // 支持的通讯协议集合
                .protocols(protocols())
                //.securityContexts(securityContexts())
                .pathMapping(swaggerProperties.getPathMapping())
                .globalRequestParameters(setFixedParameter(swaggerProperties.getReqFixedParameters()));
    }

    /**
     * 手动注册其他 docket
     *
     * @param beanFactory
     */
//    @Override
//    public void postProcessBeanFactory(DefaultListableBeanFactory beanFactory) {
//        Map<String, SwaggerProperties.GroupInfo> groupInfos = swaggerProperties.getGroupInfos();
//        System.out.println(">>>>>>>>>>>>.:" + groupInfos.toString());
//        Docket docket;
//        String groupName;
//        SwaggerProperties.GroupInfo groupInfo;
//        for (Map.Entry<String, SwaggerProperties.GroupInfo> entry : groupInfos.entrySet()) {
//            groupName = entry.getKey();
//            groupInfo = entry.getValue();
//            docket = new Docket(DocumentationType.OAS_30)
//                    .enable(swaggerProperties.isEnabled())
//                    .groupName(groupName)
//                    .globalResponses(HttpMethod.GET, responseList())
//                    .globalResponses(HttpMethod.POST, responseList())
//                    .globalResponses(HttpMethod.PUT, responseList())
//                    .globalResponses(HttpMethod.DELETE, responseList())
//                    .apiInfo(apiInfo(groupInfo.getContact()))
//                    .select()
//                    //.apis(RequestHandlerSelectors.any())
//                    //加了ApiOperation注解的类，才生成接口文档
//                    .apis(RequestHandlerSelectors.basePackage(groupInfo.getBasePackage()))
//                    .paths(PathSelectors.ant(groupInfo.getAntPath()))
//                    .build()
//                    //.securitySchemes(securitySchemes())
//                    // 支持的通讯协议集合
//                    .protocols(protocols())
//                    //.securityContexts(securityContexts())
//                    .pathMapping(swaggerProperties.getPathMapping())
//                    .globalRequestParameters(setFixedParameter(groupInfo.getReqFixedParameters()));
//            beanFactory.registerSingleton(groupName + "Docket", docket);
//        }
//    }

    /**
     * 接口请求拦截配置
     *
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addRedirectViewController("swagger", "swagger-ui/index.html");
    }

    /**
     * 通用拦截器排除swagger设置，所有拦截器都会自动加swagger相关的资源排除信息
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        Field registrationsField = FieldUtils.getField(InterceptorRegistry.class, "registrations", true);
        List<InterceptorRegistration> registrations = (List<InterceptorRegistration>) ReflectionUtils.getField(registrationsField, registry);
        if (!CollectionUtils.isEmpty(registrations)) {
            for (InterceptorRegistration interceptorRegistration : registrations) {
                interceptorRegistration
                        .excludePathPatterns(DOC_LIST);
            }
        }

    }
}
